#include "CCSkeletalSprite.h"
#include "CCAttachment.h"
#include "CCSkeletonCache.h"
#include "CCSpineGlobal.h"
#include <queue>

#include "ShaderManager.h"



CCSkeletalSprite::CCSkeletalSprite() :
m_skeleton(NULL),
m_animation(NULL),
m_loop(0),
m_paused(false),
m_rootBone(NULL),
_preAnimation(NULL),
_eventHandler(nullptr),
_skin(NULL),
_scaleX(1.0f),
_scaleY(1.0f),
_blendAnimation(NULL),
_debugBone(false),
_debugSlots(false),
_debugBoundingBox(false) {
}

CCSkeletalSprite::~CCSkeletalSprite() {
    clearBoneStates(m_rootBone);
	cleanSlotStates();
    CC_SAFE_RELEASE(m_skeleton);
    CC_SAFE_RELEASE(m_animation);
    CC_SAFE_RELEASE(_preAnimation);
    CC_SAFE_RELEASE(_blendAnimation);
    for (auto it:_rootSpriteList) {
        it->release();
    }
    _rootSpriteList.clear();
}

CCSkeletalSprite* CCSkeletalSprite::create(const char* key) {
    CCSkeleton* s=CCSkeletonCache::getInstance()->getSkeleton(key);
    assert(s);
	return create(s);
}

CCSkeletalSprite* CCSkeletalSprite::create(CCSkeleton* s) {
	CCSkeletalSprite* sprite = new CCSkeletalSprite();
	sprite->setSkeleton(s);
	return (CCSkeletalSprite*)sprite->autorelease();
}

void CCSkeletalSprite::draw() {
    for (auto it:_rootSpriteList) {
        it->draw();
    }
    
    //if (_debugBone) {
    //    glLineWidth(2);
    //    queue<CCBone*> que;
    //    que.push(m_rootBone);
    //    
    //    while (!que.empty()) {
    //        CCBone* bone=que.front();
    //        que.pop();
    //        AffineTransform t=bone->getState(this).transform;
    //        
    //        float x=bone->getLength()*t.a+t.tx;
    //        float y=bone->getLength()*-t.c+t.ty;
    //        
    //        ccDrawColor4B(255, 0, 0, 255);
    //        ccDrawLine(Point(t.tx,t.ty), Point(x,y));
    //        
    //        if (bone==m_rootBone) {
    //            ccPointSize(10);//Root bone size is 10
    //            ccDrawColor4B(0, 0, 255, 255); // Root bone is blue.
    //        }
    //        else {
    //            ccPointSize(4);
    //            ccDrawColor4B(0, 255, 0, 255);
    //        }
    //        
    //        ccDrawPoint(Point(t.tx,t.ty));
    //        
    //        for (auto it:bone->getChildren()) {
    //            que.push(it);
    //        }
    //        
    //    }
    //}
    //if (_debugSlots) {
    //    for (auto it: _rootSpriteList) {
    //        Array* ary=it->getChildren();
    //        for (int i=0; i<ary->count(); i++) {
    //            CCSpineSprite* child=(CCSpineSprite*)ary->objectAtIndex(i);
    //            if (child->isVisible() && child->getOpacity()) {
    //                child->debug();
    //            }
    //        }
    //    }
    //}
}

Point CCSkeletalSprite::getBonePosition(const char* boneName) {
    // basic check
    if(!m_skeleton) {
		return Point::ZERO;
    }
    
    // get bone
    CCBone* bone = m_skeleton->getBone(boneName);
    if(!bone) {
		return Point::ZERO;
    }
    
    // get bone node
    BoneState& state = bone->getState(this);
    
    
    AffineTransform t = state.transform;
    
    if (_isFlipX) {
        t.tx=-t.tx;
    }
    
    return Point(t.tx,t.ty);
}

Point CCSkeletalSprite::getBonePositionRelativeToWorld(const char* boneName) {
    Point pos = getBonePosition(boneName);
    return PointApplyAffineTransform(pos, getNodeToParentTransform());
}

void CCSkeletalSprite::stopAnimation(bool restore) {
	// no animation? return
	if(!m_animation) {
		return;
    }
	
	// clear animation
	m_animation->release();
	m_animation = NULL;
    
    _isDone=true;
	
	// restore original state
	if(m_rootBone && restore) {
		syncBoneStates(m_rootBone);
	}
}

void CCSkeletalSprite::playAnimation(CCSkeletalAnimation* anim,blendType type,float blendTime) {
	// stop current
	//stopAnimation(false);
	
	// hold this animation
	anim->retain();
    
    if (m_animation==anim && type==tFadeInOut) {
        CCLOG("spine warning | the animation : %s is multi play,and used fadeInOut,are you sure it's right?",anim->getName());
        type=tSwitch;
    }
    
    if (m_animation==anim) {
        return;
    }
    
    if (type==tSwitch) {
        CC_SAFE_RELEASE(m_animation);
        CC_SAFE_RELEASE_NULL(_preAnimation);
    }
    else {
        if (!_preAnimation || _blendPassTime>=_blendTime*0.5f) {
            CC_SAFE_RELEASE(_preAnimation);
            _preLoop = m_loop;
            _preFrameTime = m_frameTime;
            _preAnimation = m_animation;
        }
        else {
            CC_SAFE_RELEASE(m_animation);
        }
    }
    _type = type;
    _blendPassTime = 0.0f;
    _blendTime = blendTime;
    m_animation = anim;
    
    //init for first frame
    setupFirstFrameState();
}

void CCSkeletalSprite::playAnimation(const char* animName,blendType type,float blendTime) {
	CCSkeletalAnimation* anim = m_skeleton->getAnim(animName);
	if(anim) {
		playAnimation(anim,type,blendTime);
	}
    else {
        CCLOG("spine error | animation:%s is not find!!",animName);
    }
}

void CCSkeletalSprite::setupFirstFrameState() {
	// reset time to zero
	m_frameTime = 0;
	
	// reset flag
	m_paused = false;
    _isDone = false;
	
	// set animation to the very beginning
	//setFrame(m_frameTime);
}

void CCSkeletalSprite::setBlendAnimation(const char* animName,float alpha,bool b) {
    CCSkeletalAnimation* anim = m_skeleton->getAnim(animName);
    if (_blendAnimation) {
        _blendAnimation->release();
    }
    _blendAnimation=anim;
    _blendAnimation->retain();
    _blendAlpha=alpha;
    _blendFrameTime=0;
    _blendLoop=1;
    if (b) {
        setShaderProgram(ShaderManager::getInstace()->getShader(kShader_0));
    }
}

void CCSkeletalSprite::update(float delta,float deltaForBlend) {
	// is paused?
	if(m_paused)
		return;
	
	// basic checking
	if(!m_animation || !m_skeleton)
		return;
    
    if (_type!=tSwitch && _preAnimation && _blendPassTime+delta<=_blendTime) {
        _blendPassTime+=delta;
        float alpha=_blendPassTime/_blendTime;
        updateAnim(m_animation, m_frameTime, m_loop, delta, alpha);
        updateAnim(_preAnimation, _preFrameTime, _preLoop, delta,1.0f-alpha);
    }
    else {
        CC_SAFE_RELEASE_NULL(_preAnimation);
        updateAnim(m_animation, m_frameTime, m_loop, delta);
    }
    
    if (_blendAnimation) {
        if (deltaForBlend<0) {
            deltaForBlend=delta;
        }
        updateAnim(_blendAnimation, _blendFrameTime, _blendLoop, deltaForBlend,_blendAlpha);
        if (_blendLoop==0) {
            _blendAnimation->release();
            _blendAnimation=NULL;
			setShaderProgram(ShaderCache::getInstance()->programForKey(kCCShader_PositionTextureColor));
        }
    }
    
    syncBoneStates(m_rootBone);
}

void CCSkeletalSprite::updateAnim(CCSkeletalAnimation *animation, float &frameTime, int &loop,float delta,float alpha) {
    frameTime += delta;
    
    //anim evnet update
    if(_eventHandler) {
        CCSkeletalAnimation::EventPtrList& eList=animation->getEventPtrList();
        for (auto iter:eList) {
            EventData* data=iter;
            if (frameTime-delta<=data->time && data->time<=frameTime) {
                _eventHandler(data);
            }
        }
    }
	if(frameTime > animation->getDuration()) {
		if(loop < 0) {
			frameTime = fmod(frameTime, animation->getDuration());
		} else if(loop > 1) {
			loop--;
			frameTime = fmod(frameTime, animation->getDuration());
		} else {
            loop = 0;
            if(animation==m_animation) {
                stopAnimation(false);
            }
		}
	}
    setFrame(animation,frameTime,alpha);
}

void CCSkeletalSprite::setFrame(CCSkeletalAnimation* animation,float time,float alpha) {
	CCSkeletalAnimation::TransformPtrList& tList = animation->getTransformList();
	for(CCSkeletalAnimation::TransformPtrList::iterator iter = tList.begin(); iter != tList.end(); iter++) {
		// calculate info
		CCTransform* t = *iter;
		t->populateFrame(time);
		
		// apply frame
		t->applyTo(this,alpha);
	}
}

void CCSkeletalSprite::setSkeleton(CCSkeleton* s) {
    
    _skin=s->getSkin("default");
    
	// stop current animation
	stopAnimation(false);
	
	// hold skeleton
	s->retain();
    CC_SAFE_RELEASE(m_skeleton);
	m_skeleton = s;
	
	// clear all children node and references
	removeAllChildren();
	
	// re-create bone nodes
	m_rootBone = m_skeleton->getRootBone();
	
	// re-create slot sprite
	createSlotSprites();
    
	// sync states
    syncBoneStates(m_rootBone);
    
}

void CCSkeletalSprite::clearBoneStates(CCBone* bone) {
	bone->clearState(this);
	
	CCBone::BonePtrList& children = bone->getChildren();
	for(CCBone::BonePtrList::iterator iter = children.begin(); iter != children.end(); iter++) {
		clearBoneStates(*iter);
	}
}

// TODO: syncBoneStates
void CCSkeletalSprite::syncBoneStates(CCBone* bone) {
    BoneState &boneState = bone->getState(this);
    
    bone->nodeToParentTransform(boneState);
    
    CCBone* parent=bone->getParent();
    
    if (parent) {
        boneState.transform = AffineTransformConcat(boneState.transform, parent->getState(this).transform);
    }
    
    for (auto it: bone->getSlotList()) {
        SlotState &state = it->getState(this);
        if (state.sprite) {
            //syncAttachmentStates
			
            SlotState& original = it->getOriginalState();
            CCSpineSprite* sprite = state.sprite;
            
			if (state.activeAttachmentName!=state.preAttachmenName) {
                CCAttachment* attachment = it->getActiveAttachment(this);
                if(attachment) {
                    if (sprite) {
                        SpriteFrame* psf = attachment->createRelatedSpriteFrame();
                        sprite->setDisplayFrame(psf);
                        attachment->synchro(sprite);
                        if (!state.preAttachmenName) {
                            sprite->setVisible(true);
                        }
                    }
                    else {
                        log("spine error todo |  the sprite not be create it setup,and the sprite maybe has wrong zOrder!!!");
                        assert(0);
                    }
                }
                else {//attachmente==NULL
                    if (sprite) {
                        sprite->setVisible(false);
                    }
                    else {
                        CCLOG("spine error todo |  the sprite is NULL and attachmente==NULL");
                    }
                }
                state.preAttachmenName=state.activeAttachmentName;
            }

            if (sprite) {
                sprite->setColor(state.color);
            }
            
            state.activeAttachmentName=original.activeAttachmentName;
            state.color=original.color;
            
            //syncBoneStates
            state.sprite->setTranformToBatch(AffineTransformConcat(state.sprite->getNodeToParentTransform(),boneState.transform));
        }
    }
    
    for (auto it: bone->getChildren()) {
		syncBoneStates(it);
	}
}

void CCSkeletalSprite::cleanSlotStates() {
	CCSkeleton::SlotPtrList& slotDisplayList = m_skeleton->getSlotDisplayList();
	for(CCSkeleton::SlotPtrList::iterator iter = slotDisplayList.begin(); iter != slotDisplayList.end(); iter++) {
		(*iter)->clearState(this);
	}
}

void CCSkeletalSprite::createSprites(Sprite* sprite,bool &lastAdditive,bool &nowAdditive) {
    SpriteBatchNode* rootSprite=NULL;
    if (_rootSpriteList.empty()) {
        rootSprite = SpriteBatchNode::createWithTexture(sprite->getTexture());
        _rootSpriteList.push_back(rootSprite);
        rootSprite->retain();
        lastAdditive=nowAdditive;
        if (nowAdditive) {
            BlendFunc blendFunc;
            blendFunc.src=GL_SRC_ALPHA;
            blendFunc.dst=GL_ONE;
            rootSprite->setBlendFunc(blendFunc);
        }
    }
    else {
        rootSprite=(SpriteBatchNode*)*_rootSpriteList.rbegin();
        if (rootSprite->getTexture()->getName()!=sprite->getTexture()->getName() || lastAdditive!=nowAdditive) {
            rootSprite=SpriteBatchNode::createWithTexture(sprite->getTexture());
            _rootSpriteList.push_back(rootSprite);
            rootSprite->retain();
            if (nowAdditive) {
                BlendFunc blendFunc;
                blendFunc.src=GL_SRC_ALPHA;
                blendFunc.dst=GL_ONE;
                rootSprite->setBlendFunc(blendFunc);
            }
            lastAdditive=nowAdditive;
        }
    }
    rootSprite->addChild(sprite);
}

void CCSkeletalSprite::createSlotSprites() {//TODO?   create SlotSprite when attachment==NULL
    
    bool lastAdditive;
    
	CCSkeleton::SlotPtrList& slotDisplayList = m_skeleton->getSlotDisplayList();
	for(CCSkeleton::SlotPtrList::iterator iter = slotDisplayList.begin(); iter != slotDisplayList.end(); iter++) {
        
		// get slot active attachment image file name
		CCSlot* slot = *iter;
		// create slot sprite
		SlotState& state = slot->getState(this);
        CCAttachment* attachment = slot->getActiveAttachment(this);
        
        bool nowAdditive=slot->getOriginalState().additive;
        
		if(attachment) {
            SpriteFrame* psf = attachment->createRelatedSpriteFrame();
			CCSpineSprite* sprite = CCSpineSprite::createWithSpriteFrame(psf);
            attachment->synchro(sprite);
            state.sprite = sprite;
            state.preAttachmenName=state.activeAttachmentName;
            createSprites(sprite,lastAdditive,nowAdditive);
		}
	}
}

void CCSkeletalSprite::setBonePosition(const char* boneName, float x, float y, bool fixed) {
    // basic check
    if(!m_skeleton)
        return;
    
    // get bone
    CCBone* bone = m_skeleton->getBone(boneName);
    if(!bone)
        return;
    
    // set
    BoneState& state = bone->getState(this);
    state.x = x;
    state.y = y;
    if(fixed)
        bone->addFlag(CCBone::FIXED_POSITION);
    else
        bone->removeFlag(CCBone::FIXED_POSITION);
}

void CCSkeletalSprite::setBoneRotation(const char* boneName, float angle, bool fixed) {
    // basic check
    if(!m_skeleton)
        return;
    
    // get bone
    CCBone* bone = m_skeleton->getBone(boneName);
    if(!bone)
        return;
    
    // set
    BoneState& state = bone->getState(this);
    state.rotation = angle;
    if(fixed)
        bone->addFlag(CCBone::FIXED_ROTATION);
    else
        bone->removeFlag(CCBone::FIXED_ROTATION);
}

void CCSkeletalSprite::setBoneScale(const char* boneName, float scaleX, float scaleY, bool fixed) {
    // basic check
    if(!m_skeleton)
        return;
    
    // get bone
    CCBone* bone = m_skeleton->getBone(boneName);
    if(!bone)
        return;
    
    // set
    BoneState& state = bone->getState(this);
    state.scaleX = scaleX;
    state.scaleY = scaleY;
    if(fixed)
        bone->addFlag(CCBone::FIXED_SCALE);
    else
        bone->removeFlag(CCBone::FIXED_SCALE);
}

void CCSkeletalSprite::setSlotColor(const char* slotName, const Color4B &color, bool fixed) {
    // basic check
    if(!m_skeleton)
        return;
    
    // get slot
    CCSlot* slot = m_skeleton->getSlot(slotName);
    if(!slot)
        return;
    
    // set
    SlotState& state = slot->getState(this);
    state.color = color;
    state.sprite->setColor(color);
    if(fixed)
        slot->addFlag(CCSlot::FIXED_COLOR);
    else
        slot->removeFlag(CCSlot::FIXED_COLOR);
}

void CCSkeletalSprite::setSlotAttachment(const char* slotName, const char* attachmentName, bool fixed) {
    // basic check
    if(!m_skeleton)
        return;
    
    // get slot
    CCSlot* slot = m_skeleton->getSlot(slotName);
    if(!slot)
        return;
    
    // set
    SlotState& state = slot->getState(this);
    CCAttachment* attachment = slot->getActiveAttachment(this);
    if(attachment) {
        SpriteFrame* psf = attachment->createRelatedSpriteFrame();
		state.sprite->setDisplayFrame(psf);
        if(fixed)
            slot->addFlag(CCSlot::FIXED_ATTACHMENT);
        else
            slot->removeFlag(CCSlot::FIXED_ATTACHMENT);
    }
    else {
        CCLOG("CCSkeletalSprite::setSlotAttachment: the attachment %s is not found", attachmentName);
    }
}

void CCSkeletalSprite::applySkin(const char* skinName) {
    // basic check
    if(!m_skeleton)
        return;
    
    // get skin
    CCSkin* skin = m_skeleton->getSkin(skinName);
    if(!skin) {
        CCLOG("CCSkeletalSprite::applySkin: the skin %s is not found", skinName);
        return;
    }
    cleanOldSkin();
    
    _skin=skin;
    
    createSlotSprites();
    
    syncBoneStates(m_rootBone);
}

void CCSkeletalSprite::cleanOldSkin() {
    CCSkeleton::SlotPtrList& slotDisplayList = m_skeleton->getSlotDisplayList();
	for(CCSkeleton::SlotPtrList::iterator iter = slotDisplayList.begin(); iter != slotDisplayList.end(); iter++) {
		CCSlot* slot = *iter;
		SlotState& state = slot->getState(this);
        if (state.sprite) {
            state.sprite=NULL;
        }
	}
    for (auto it:_rootSpriteList) {
        it->removeAllChildren();
        it->release();
    }
    _rootSpriteList.clear();
}

void CCSkeletalSprite::setFlipX(bool bFlipX) {
    _isFlipX=bFlipX;
    for (auto it:_rootSpriteList) {
        Array* ary=it->getChildren();
        for (size_t i= 0; i < ary->count(); i++) {
            CCSpineSprite* child=(CCSpineSprite*)ary->objectAtIndex(i);
            child->setFlipX(bFlipX);
        }
    }
}

void CCSkeletalSprite::setShaderProgram(cocos2d::GLProgram *pShaderProgram) {//TODO
    for (auto it: _rootSpriteList) {
        it->setShaderProgram(pShaderProgram);
    }
}

const Point& CCSkeletalSprite::getLogicPosition() {
    BoneState& state=m_rootBone->getState(this);
    float x=state.transform.tx;
    float y=state.transform.ty;
    _logicPosition=Point(x,y);
    return _logicPosition;
}

void CCSkeletalSprite::setLogicPosition(Point const & pos) {
	_logicPosition = pos;
    setPosition(Point(_logicPosition.x * _scaleX, _logicPosition.y * _scaleY));
}

Rect CCSkeletalSprite::boundingBox() {
    
    float minX = FLT_MAX, minY = FLT_MAX, maxX = FLT_MIN, maxY = FLT_MIN;
    
    for(auto it: m_skeleton->getSlotDisplayList()) {
        CCSpineSprite* sprite=it->getState(this).sprite;
        if (sprite && sprite->isVisible() && sprite->getOpacity()) {
            V3F_C4B_T2F_Quad quad=sprite->getQuad();
            Point vertices[4] = {
                Point( quad.bl.vertices.x, quad.bl.vertices.y ),
                Point( quad.br.vertices.x, quad.br.vertices.y ),
                Point( quad.tr.vertices.x, quad.tr.vertices.y ),
                Point( quad.tl.vertices.x, quad.tl.vertices.y ),
            };
            for (int i=0; i<4; i++) {
                minX=min(minX,vertices[i].x);
                minY=min(minY,vertices[i].y);
                maxX=max(maxX,vertices[i].x);
                maxY=max(maxY,vertices[i].y);
            }
        }
    }
    
    Rect rect=Rect(minX, minY, maxX-minX, maxY-minY);
    
	return RectApplyAffineTransform(rect,getNodeToParentTransform());
}

void CCSkeletalSprite::visit() {
    Node::visit();
    //if (_debugBoundingBox) {
    //    Rect rect=boundingBox();
    //    
    //    ccDrawColor4B(0, 255, 255, 255);
    //    
    //    // draw bounding box
    //    Point vertices[4] = {
    //        Point( rect.getMinX(), rect.getMinY() ),
    //        Point( rect.getMaxX(), rect.getMinY() ),
    //        Point( rect.getMaxX(), rect.getMaxY() ),
    //        Point( rect.getMinX(), rect.getMaxY() )
    //    };
    //    ccDrawPoly(vertices, 4, true);
    //}
}


